{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZwZNOAMZcxl3"
      },
      "source": [
        "##### Copyright 2019 Google LLC"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "nxbcnXODdE06"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-BszoQj0dSZO"
      },
      "source": [
        "# 이미지 분류를 위한 적대적 정규화"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wfqlePz0g6o5"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">}TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/neural_structured_learning/tutorials/adversarial_keras_cnn_mnist.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">View source on GitHub</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oHEGl8h_m6tS"
      },
      "source": [
        "## 개요\n",
        "\n",
        "이 튜토리얼에서는 Neural Structured Learning(NSL) 프레임워크를 사용하여 이미지 분류에 적대적 학습([Goodfellow et al., 2014](https://arxiv.org/abs/1412.6572))을 사용하는 방법을 살펴봅니다.\n",
        "\n",
        "적대적 학습의 핵심 아이디어는 유기적 훈련 데이터와 함께 적대적으로 교란된 데이터(적대적 예라고 함)로 모델을 훈련하는 것입니다. 적대적인 예는 의도적으로 모델을 잘못된 예측 또는 분류로 오도하기 위해 구성됩니다. 이러한 예를 사용한 훈련을 통해 모델은 예측할 때 적대적 교란에 대해 견고함을 유지하도록 학습합니다.\n",
        "\n",
        "이 튜토리얼에서는 Neural Structured Learning 프레임워크를 사용하여 견고한 모델을 얻기 위해 적대적 학습을 적용하는 다음 절차를 설명합니다.\n",
        "\n",
        "1. 신경망을 기본 모델로 생성합니다. 이 튜토리얼에서 기본 모델은 `tf.keras` 함수형 API로 생성됩니다. 이 절차는 `tf.keras` 순차 및 하위 클래스화 API로 생성된 모델과도 호환됩니다.\n",
        "2. NSL 프레임워크에서 제공하는 **`AdversarialRegularization`** 래퍼 클래스로 기본 모델을 래핑하여 새 `tf.keras.Model` 인스턴스를 만듭니다. 이 새로운 모델은 훈련 목표에서 정규화 항으로 적대적 손실을 포함합니다.\n",
        "3. 훈련 데이터의 예를 특성 사전으로 변환합니다.\n",
        "4. 새 모델을 훈련하고 평가합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VgSOF-49Q7kS"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4RhmgQ7-mlrl"
      },
      "source": [
        "Neural Structured Learning 패키지를 설치합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ByJ7133BQULR"
      },
      "outputs": [],
      "source": [
        "!pip install --quiet neural-structured-learning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PZvsEQrhSqKx"
      },
      "source": [
        "라이브러리를 가져옵니다. `neural_structured_learning`을 `nsl`로 축약합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EuqEuAYzTMo0"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import neural_structured_learning as nsl\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import tensorflow_datasets as tfds"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3LwBtQGaTvbe"
      },
      "source": [
        "## 하이퍼 매개변수\n",
        "\n",
        "모델 훈련 및 평가를 위해 하이퍼 매개변수(`HParams` 객체에서)를 수집하고 설명합니다.\n",
        "\n",
        "입출력:\n",
        "\n",
        "- **`input_shape`**: 입력 텐서의 형상, 각 이미지는 1채널의 28x28 픽셀입니다.\n",
        "- **`num_classes`**: 총 10개의 클래스가 있으며 10자리 [0-9]에 해당합니다.\n",
        "\n",
        "모델 아키텍처:\n",
        "\n",
        "- **`conv_filters`**: 각각 컨볼루셔널 레이어에서 필터의 수를 지정하는 숫자의 목록입니다.\n",
        "- **`kernel_size`**: 모든 컨볼루셔널 레이어가 공유하는 2D 컨볼루션 창의 크기입니다.\n",
        "- **`pool_size`**: 각 최대 풀링 레이어에서 이미지를 축소하는 요소입니다.\n",
        "- **`num_fc_units`**: 완전히 연결된 각 레이어의 단위(예: 너비) 수입니다.\n",
        "\n",
        "훈련 및 평가:\n",
        "\n",
        "- **`batch_size`**: 훈련 및 평가에 사용되는 배치 크기입니다.\n",
        "- **`epochs`**: 훈련 epoch의 수입니다.\n",
        "\n",
        "적대적 학습:\n",
        "\n",
        "- **`adv_multiplier`**: 훈련 목표에서 레이블이 지정된 손실에 대한 적대적 손실의 가중치입니다.\n",
        "- **`adv_step_size`** : 적대적 교란의 크기입니다.\n",
        "- **`adv_grad_norm`**: 적대적 교란의 크기를 측정하기 위한 표준입니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iOc8YdmIRSHo"
      },
      "outputs": [],
      "source": [
        "class HParams(object):\n",
        "  def __init__(self):\n",
        "    self.input_shape = [28, 28, 1]\n",
        "    self.num_classes = 10\n",
        "    self.conv_filters = [32, 64, 64]\n",
        "    self.kernel_size = (3, 3)\n",
        "    self.pool_size = (2, 2)\n",
        "    self.num_fc_units = [64]\n",
        "    self.batch_size = 32\n",
        "    self.epochs = 5\n",
        "    self.adv_multiplier = 0.2\n",
        "    self.adv_step_size = 0.2\n",
        "    self.adv_grad_norm = 'infinity'\n",
        "\n",
        "HPARAMS = HParams()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "72zL1AMcYYGG"
      },
      "source": [
        "## MNIST 데이터세트\n",
        "\n",
        "[MNIST 데이터세트](http://yann.lecun.com/exdb/mnist/)에는 손으로 쓴 숫자('0'에서 '9'까지)의 회색조 이미지가 포함됩니다. 각 이미지는 저해상도(28x28 픽셀)에서 한 자리를 표시합니다. 관련된 작업은 이미지를 숫자당 하나씩 10개의 범주로 분류하는 것입니다.\n",
        "\n",
        "여기서는 [TensorFlow Datasets](https://www.tensorflow.org/datasets)에서 MNIST 데이터세트를 로드합니다. 데이터 다운로드 및 `tf.data.Dataset` 생성을 처리합니다. 로드된 데이터세트에는 두 개의 하위 집합이 있습니다.\n",
        "\n",
        "- 60,000개의 예제로 `train`하고\n",
        "- 10,000개의 예제로 `test`합니다.\n",
        "\n",
        "두 하위 집합의 예는 다음 두 키를 사용하여 특성 사전에 저장됩니다.\n",
        "\n",
        "- `image`: 0에서 255 사이의 픽셀 값 배열입니다.\n",
        "- `label`: Groundtruth 레이블, 범위는 0~9입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R1dK6E4axNHB"
      },
      "outputs": [],
      "source": [
        "datasets = tfds.load('mnist')\n",
        "\n",
        "train_dataset = datasets['train']\n",
        "test_dataset = datasets['test']\n",
        "\n",
        "IMAGE_INPUT_NAME = 'image'\n",
        "LABEL_INPUT_NAME = 'label'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IBkh4mbsxLR_"
      },
      "source": [
        "모델을 수치적으로 안정적으로 만들기 위해 `normalize` 함수에 데이터세트를 매핑하여 픽셀 값을 [0, 1]로 정규화합니다. 훈련 세트를 셔플하고 일괄 처리한 후 기본 모델의 훈련을 위해 예를 특성 튜플 `(image, label)`로 변환합니다. 또한, 나중에 사용할 수 있도록 튜플에서 사전으로 변환하는 함수를 제공합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VhMEJqKs0_7z"
      },
      "outputs": [],
      "source": [
        "def normalize(features):\n",
        "  features[IMAGE_INPUT_NAME] = tf.cast(\n",
        "      features[IMAGE_INPUT_NAME], dtype=tf.float32) / 255.0\n",
        "  return features\n",
        "\n",
        "def convert_to_tuples(features):\n",
        "  return features[IMAGE_INPUT_NAME], features[LABEL_INPUT_NAME]\n",
        "\n",
        "def convert_to_dictionaries(image, label):\n",
        "  return {IMAGE_INPUT_NAME: image, LABEL_INPUT_NAME: label}\n",
        "\n",
        "train_dataset = train_dataset.map(normalize).shuffle(10000).batch(HPARAMS.batch_size).map(convert_to_tuples)\n",
        "test_dataset = test_dataset.map(normalize).batch(HPARAMS.batch_size).map(convert_to_tuples)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JrrMpPNmpCKK"
      },
      "source": [
        "## 기본 모델\n",
        "\n",
        "기본 모델은 3개의 컨볼루셔널 레이어와 2개의 완전 연결 레이어(`HPARAMS`에 정의된 대로)로 구성된 신경망입니다. 여기에서는 Keras 함수형 API를 사용하여 정의합니다. 다른 API 또는 모델 아키텍처를 자유롭게 사용해 보세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4UjrtuIsYWo3"
      },
      "outputs": [],
      "source": [
        "def build_base_model(hparams):\n",
        "  \"\"\"Builds a model according to the architecture defined in `hparams`.\"\"\"\n",
        "  inputs = tf.keras.Input(\n",
        "      shape=hparams.input_shape, dtype=tf.float32, name=IMAGE_INPUT_NAME)\n",
        "\n",
        "  x = inputs\n",
        "  for i, num_filters in enumerate(hparams.conv_filters):\n",
        "    x = tf.keras.layers.Conv2D(\n",
        "        num_filters, hparams.kernel_size, activation='relu')(\n",
        "            x)\n",
        "    if i < len(hparams.conv_filters) - 1:\n",
        "      # max pooling between convolutional layers\n",
        "      x = tf.keras.layers.MaxPooling2D(hparams.pool_size)(x)\n",
        "  x = tf.keras.layers.Flatten()(x)\n",
        "  for num_units in hparams.num_fc_units:\n",
        "    x = tf.keras.layers.Dense(num_units, activation='relu')(x)\n",
        "  pred = tf.keras.layers.Dense(hparams.num_classes, activation='softmax')(x)\n",
        "  model = tf.keras.Model(inputs=inputs, outputs=pred)\n",
        "  return model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "288nsmN5pLoo"
      },
      "outputs": [],
      "source": [
        "base_model = build_base_model(HPARAMS)\n",
        "base_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mlTUGn1t_HAr"
      },
      "source": [
        "다음으로 기본 모델을 훈련하고 평가합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K2cFDbmRpRMp"
      },
      "outputs": [],
      "source": [
        "base_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',\n",
        "                   metrics=['acc'])\n",
        "base_model.fit(train_dataset, epochs=HPARAMS.epochs)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "J94Y_WTaqAsi"
      },
      "outputs": [],
      "source": [
        "results = base_model.evaluate(test_dataset)\n",
        "named_results = dict(zip(base_model.metrics_names, results))\n",
        "print('\\naccuracy:', named_results['acc'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c8OClWqGALIm"
      },
      "source": [
        "기본 모델이 테스트세트에서 99% 정확성을 달성하는 것을 볼 수 있습니다. 아래의 [적대적 교란에서의 견고성](#scrollTo=HXK9MGG8lBX3)에서 얼마나 견고한지 확인할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CemXA8N9q336"
      },
      "source": [
        "## 적대적 정규화 모델\n",
        "\n",
        "여기에서는 NSL 프레임워크를 사용하여 몇 줄의 코드로 적대적 훈련을 Keras 모델에 통합하는 방법을 보여줍니다. 기본 모델은 훈련 목표에 적대적 정규화를 포함하는 새로운 `tf.Keras.Model`을 만들기 위해 래핑됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YUOpl-rkzRrY"
      },
      "source": [
        "먼저, 도우미 함수 `nsl.configs.make_adv_reg_config`를 사용하여 모든 관련 하이퍼 매개변수가 포함된 config 객체를 만듭니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-WWVwJB2qstE"
      },
      "outputs": [],
      "source": [
        "adv_config = nsl.configs.make_adv_reg_config(\n",
        "    multiplier=HPARAMS.adv_multiplier,\n",
        "    adv_step_size=HPARAMS.adv_step_size,\n",
        "    adv_grad_norm=HPARAMS.adv_grad_norm\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OmeIUyxE4s68"
      },
      "source": [
        "이제 `AdversarialRegularization`으로 기본 모델을 래핑할 수 있습니다. 여기에서 새로운 기본 모델 (`base_adv_model`)을 생성하여 기존 모델(`base_model`)을 나중에 비교하는 데 사용할 수 있습니다.\n",
        "\n",
        "반환된 `adv_model`은 `tf.keras.Model` 객체이며, 훈련 목표에는 적대적 손실에 대한 정규화 항이 포함됩니다. 손실을 계산하려면 모델이 일반 입력(특성 `image`) 외에도 레이블 정보(특성 `label`)에 액세스할 수 있어야 합니다. 이러한 이유로 데이터세트의 예를 튜플에서 다시 사전으로 변환합니다. 그리고 `label_keys` 매개변수를 통해 레이블 정보가 포함된 특성을 모델에 알립니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TObqJLEX4sQq"
      },
      "outputs": [],
      "source": [
        "base_adv_model = build_base_model(HPARAMS)\n",
        "adv_model = nsl.keras.AdversarialRegularization(\n",
        "    base_adv_model,\n",
        "    label_keys=[LABEL_INPUT_NAME],\n",
        "    adv_config=adv_config\n",
        ")\n",
        "\n",
        "train_set_for_adv_model = train_dataset.map(convert_to_dictionaries)\n",
        "test_set_for_adv_model = test_dataset.map(convert_to_dictionaries)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aKTQWzfj7JvL"
      },
      "source": [
        "다음으로 적대적 정규화 모델을 컴파일, 훈련 및 평가합니다. \"손실 사전에서 누락된 출력\"과 같은 경고가 있을 수 있으며, `adv_model`이 총 손실을 계산하기 위해 기본 구현에 의존하지 않기 때문에 괜찮습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aTSK-cHbuWDw"
      },
      "outputs": [],
      "source": [
        "adv_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',\n",
        "                   metrics=['acc'])\n",
        "adv_model.fit(train_set_for_adv_model, epochs=HPARAMS.epochs)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3v_Jn7wuviZx"
      },
      "outputs": [],
      "source": [
        "results = adv_model.evaluate(test_set_for_adv_model)\n",
        "named_results = dict(zip(adv_model.metrics_names, results))\n",
        "print('\\naccuracy:', named_results['sparse_categorical_accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LgnslZYk9Acg"
      },
      "source": [
        "적대적 정규화 모델도 테스트세트에서 매우 우수한 것을 볼 수 있습니다(99% 정확성)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HXK9MGG8lBX3"
      },
      "source": [
        "## 적대적 교란에서의 견고성\n",
        "\n",
        "이제 적대적 교란에서의 견고성을 위해 기본 모델과 적대적 정규화 모델을 비교합니다.\n",
        "\n",
        "`AdversarialRegularization.perturb_on_batch` 함수를 사용하여 적대적으로 교란된 예를 생성합니다. 그리고 기본 모델을 기반으로 한 세대를 사용합니다. 이를 위해 기본 모델을 `AdversarialRegularization`으로 래핑합니다. 훈련(`Model.fit`)을 호출하지 않는 한, 모델에서 학습된 변수는 변경되지 않으며, 모델은 여전히 [기본 모델](#scrollTo=JrrMpPNmpCKK) 섹션에서와 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FLkYw54pvxJO"
      },
      "outputs": [],
      "source": [
        "reference_model = nsl.keras.AdversarialRegularization(\n",
        "    base_model,\n",
        "    label_keys=[LABEL_INPUT_NAME],\n",
        "    adv_config=adv_config)\n",
        "reference_model.compile(\n",
        "    optimizer='adam',\n",
        "    loss='sparse_categorical_crossentropy',\n",
        "    metrics=['acc'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DR0Rn5rxBeDh"
      },
      "source": [
        "평가할 모델을 사전에 수집하고 각 모델에 대한 메트릭 객체를 만듭니다.\n",
        "\n",
        "기본 모델과 같은 입력 형식(레이블 정보 필요 없음)을 갖기 위해 `adv_model.base_model`을 사용합니다. `adv_model.base_model`에서 학습된 변수는 `adv_model`에서의 변수와 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "igRBxPlPm_JE"
      },
      "outputs": [],
      "source": [
        "models_to_eval = {\n",
        "    'base': base_model,\n",
        "    'adv-regularized': adv_model.base_model\n",
        "}\n",
        "metrics = {\n",
        "    name: tf.keras.metrics.SparseCategoricalAccuracy()\n",
        "    for name in models_to_eval.keys()\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BAPYegAbC8mZ"
      },
      "source": [
        "다음은 교란된 예를 생성하고 이를 사용하여 모델을 평가하는 루프입니다. 다음 섹션에서 시각화를 위해 교란된 이미지, 레이블 및 예측값을 저장합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IGnLXhswmUN8"
      },
      "outputs": [],
      "source": [
        "perturbed_images, labels, predictions = [], [], []\n",
        "\n",
        "for batch in test_set_for_adv_model:\n",
        "  perturbed_batch = reference_model.perturb_on_batch(batch)\n",
        "  # Clipping makes perturbed examples have the same range as regular ones.\n",
        "  perturbed_batch[IMAGE_INPUT_NAME] = tf.clip_by_value(                          \n",
        "      perturbed_batch[IMAGE_INPUT_NAME], 0.0, 1.0)\n",
        "  y_true = perturbed_batch.pop(LABEL_INPUT_NAME)\n",
        "  perturbed_images.append(perturbed_batch[IMAGE_INPUT_NAME].numpy())\n",
        "  labels.append(y_true.numpy())\n",
        "  predictions.append({})\n",
        "  for name, model in models_to_eval.items():\n",
        "    y_pred = model(perturbed_batch)\n",
        "    metrics[name](y_true, y_pred)\n",
        "    predictions[-1][name] = tf.argmax(y_pred, axis=-1).numpy()\n",
        "\n",
        "for name, metric in metrics.items():\n",
        "  print('%s model accuracy: %f' % (name, metric.result().numpy()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S5cC3XbRGFJQ"
      },
      "source": [
        "입력이 적대적으로 교란될 때 기본 모델의 정확성이 급격히 떨어지는 것을 볼 수 있습니다(99%에서 약 50%로). 반면에 적대적 정규화 모델의 정확성은 약간만 저하됩니다(99%에서 95%로). 이는 모델의 견고성 향상에 대한 적대적 학습의 효과를 보여줍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YfB5oBBfWLRK"
      },
      "source": [
        "## 적대적으로 교란된 이미지의 예\n",
        "\n",
        "다음에서 적대적으로 교란된 이미지를 살펴봅니다. 교란된 이미지는 여전히 사람이 인식할 수 있는 숫자를 보여주지만, 기본 모델을 속일 수 있음을 알 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3iK9vO_xKJfg"
      },
      "outputs": [],
      "source": [
        "batch_index = 0\n",
        "\n",
        "batch_image = perturbed_images[batch_index]\n",
        "batch_label = labels[batch_index]\n",
        "batch_pred = predictions[batch_index]\n",
        "\n",
        "batch_size = HPARAMS.batch_size\n",
        "n_col = 4\n",
        "n_row = (batch_size + n_col - 1) / n_col\n",
        "\n",
        "print('accuracy in batch %d:' % batch_index)\n",
        "for name, pred in batch_pred.items():\n",
        "  print('%s model: %d / %d' % (name, np.sum(batch_label == pred), batch_size))\n",
        "\n",
        "plt.figure(figsize=(15, 15))\n",
        "for i, (image, y) in enumerate(zip(batch_image, batch_label)):\n",
        "  y_base = batch_pred['base'][i]\n",
        "  y_adv = batch_pred['adv-regularized'][i]\n",
        "  plt.subplot(n_row, n_col, i+1)\n",
        "  plt.title('true: %d, base: %d, adv: %d' % (y, y_base, y_adv))\n",
        "  plt.imshow(tf.keras.preprocessing.image.array_to_img(image), cmap='gray')\n",
        "  plt.axis('off')\n",
        "\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g_vo1pWYJlHP"
      },
      "source": [
        "## 결론\n",
        "\n",
        "Neural Structured Learning(NSL) 프레임워크를 사용하여 이미지 분류를 위한 적대적 학습을 사용하는 방법을 시연했습니다. 사용자가 다양한 적대적 설정(하이퍼 매개변수에서)을 실험하고 모델 견고성에 어떻게 영향을 미치는지 확인할 것을 권장합니다."
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "adversarial_keras_cnn_mnist.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
